package com.moralesce.gt.model;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.moralesce.gt.math.Combination;
import com.moralesce.gt.util.ListUtil;

/**
 * Two cards, also known as hole cards or pocket cards, which belong solely to
 * one player and remain hidden from the other players.
 * 
 * @author Brandon Grenier
 */
public class StartingHand {

	private final Integer key;
	private final Integer id;
	private final String value;
	private List<Card> cards;

	public static StartingHand newInstance(List<Card> cards) {
		return new StartingHand(cards);
	}

	public static StartingHand newInstance(Card[] cards) {
		return new StartingHand(Arrays.asList(cards));
	}

	public static StartingHand newInstance(String value) {
		if (value.length() != 4) {
			throw new IllegalArgumentException("A hand must be created with a 4 character value");
		}

		List<Card> cards = new ArrayList<Card>();
		cards.add(new Card(String.valueOf(value.charAt(0)) + String.valueOf(value.charAt(1))));
		cards.add(new Card(String.valueOf(value.charAt(2)) + String.valueOf(value.charAt(3))));
		return new StartingHand(cards);
	}

	private StartingHand(List<Card> cards) {
		if (cards.size() != 2) {
			throw new IllegalArgumentException("Cannot create a hand with " + cards.size() + " cards - a starting hand must have 2 cards");
		}
		if (ListUtil.containsDuplicates(cards)) {
			throw new IllegalArgumentException("There are duplicate cards in the list.");
		}
		this.cards = ListUtil.sort(cards);

		StringBuilder builder = new StringBuilder();
		for (Card card : cards) {
			builder.append(card.value());
		}
		this.value = builder.toString();

		Integer i = 1;
		for (Card card : cards) {
			i = i * card.rank().id();
		}
		this.key = this.suited() ? i * 67 : i;

		Integer j = 1;
		for (Card card : cards) {
			j = j * card.id();
		}
		this.id = j;
	}

	/**
	 * Returns the starting hand id.
	 * 
	 * @return The starting hand id.
	 */
	public Integer id() {
		return id;
	}

	/**
	 * Returns the starting hand key.
	 * 
	 * @return The starting hand key.
	 */
	public Integer key() {
		return key;
	}

	/**
	 * Returns the starting hand value.
	 * 
	 * @return The starting hand value.
	 */
	public String value() {
		return value;
	}

	/**
	 * Returns the shorthand notation for the starting hand.
	 * 
	 * <p>
	 * Often it is desirable to have a short hand notation for starting hands,
	 * ignoring card suits and simply describing whether the starting hand is
	 * suited or offsuit. The shorthand notation removes suit characters, and
	 * appends an 'o' (offsuit) or 's' (suited) to the card ranks.
	 * </p>
	 * 
	 * Starting hand consisting of the Ace of Clubs and Jack of Clubs:<br/>
	 * value() == 'ACJC', shortValue() == 'JCs' <br/>
	 * <br/>
	 * 
	 * Starting hand consisting of the Ten of Hearts and Eight of Clubs:<br/>
	 * value() == 'TH8C', shortValue() == 'T8o' <br/>
	 * 
	 * @return The shorthand notation for the starting hand.
	 */
	public String shortValue() {
		String name = "";
		for (Card card : cards) {
			name += card.rank().value();
		}
		return name + (suited() ? "s" : "o");
	}

	/**
	 * Returns all cards in this starting hand.
	 * 
	 * @return A List<Card> of all cards in this starting hand.
	 */
	public List<Card> cards() {
		return Collections.unmodifiableList(cards);
	}

	/**
	 * Determines if this starting hand is suited.
	 * 
	 * @return true if this starting hand is suited, false otherwise.
	 */
	public boolean suited() {
		for (int i = 0; i < cards.size() - 1; i++) {
			if (!cards.get(i).suit().equals(cards.get(i + 1).suit())) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Returns all possible starting hands.
	 * 
	 * <p>
	 * There are exaclty 1,326 (52c2) 2-card starting hands. This method returns
	 * a list containing every possible starting hand.
	 * </p>
	 * 
	 * @return A List<StartingHand> of all possible starting hands.
	 */
	public static List<StartingHand> listStartingHands() {
		return ALL_STARTING_HANDS;
	}

	/**
	 * Returns all distinct starting hands.
	 * 
	 * <p>
	 * While there are 1,324 starting hands, many of these starting hands have
	 * the same value in Texas hold’em. To elaborate on this a bit, consider the
	 * number of hands a player could have containing a Jack and a Seven:
	 * </p>
	 * 
	 * J♣ 7♦ <br/>
	 * J♠ 7♦ <br/>
	 * J♥ 7♦ <br/>
	 * J♦ 7♦ <br/>
	 * J♣ 7♠ <br/>
	 * J♠ 7♠ <br/>
	 * J♥ 7♠ <br/>
	 * J♦ 7♠ <br/>
	 * J♣ 4♥ <br/>
	 * J♠ 4♥ <br/>
	 * … <br/>
	 * 
	 * <p>
	 * This list goes on a bit further further. You might be surprised to know
	 * that there are 16 starting hand combinations that contain exactly one
	 * Jack and one Seven. Out of this list of 16, only two card values have any
	 * relevance in a poker game - Jack-Seven suited and Jack-Seven unsuited.
	 * </p>
	 * 
	 * <p>
	 * This exercise demonstrates that while there are 1,324 starting hands to
	 * contend with, the number of distinct starting hands is dramatically
	 * lower. This is because card suits don’t tend to affect the score of the
	 * hand.
	 * </p>
	 * 
	 * <p>
	 * Once all 1,324 poker hands are collapsed into distinct values, we end up
	 * with just 169 starting hands!
	 * </p>
	 * 
	 * @return A List<StartingHand> of all distinct starting hands.
	 */
	public static List<StartingHand> listDistinctStartingHands() {
		return new ArrayList<StartingHand>(DISTINCT_STARTING_HANDS.values());
	}

	private static final Map<Integer, StartingHand> DISTINCT_STARTING_HANDS = new HashMap<Integer, StartingHand>();
	private static final List<StartingHand> ALL_STARTING_HANDS = new ArrayList<StartingHand>();
	static {
		List<List<Card>> allPossibleStartingCards = new Combination(new Deck().cards(), 2).list();
		for (List<Card> startingCards : allPossibleStartingCards) {
			StartingHand startingHand = StartingHand.newInstance(startingCards);
			DISTINCT_STARTING_HANDS.put(startingHand.key, startingHand);
			ALL_STARTING_HANDS.add(startingHand);
		}
	}

}
